/**
 *
 */
package com.typhoon.jdbcTemplateHA.dao.ha;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.log4j.Logger;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.CallableStatementCallback;
import org.springframework.jdbc.core.CallableStatementCreator;
import org.springframework.jdbc.core.ConnectionCallback;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.ParameterizedPreparedStatementSetter;
import org.springframework.jdbc.core.PreparedStatementCallback;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.StatementCallback;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.jdbc.support.rowset.SqlRowSet;

/**
 * 高可用jdbcOperations
 * 
 * @author Typhoon
 * @date 2017-09-17 13:54 Sunday
 * @since V2.0
 */
public class HABalanceSimpleJdbcOperations implements PropertyChangeListener, JdbcOperations {

    /**
     * LOG
     */
	private static Logger log = Logger.getLogger(HABalanceSimpleJdbcOperations.class);
   // private static org.slf4j.Logger log = LoggerFactory.getLogger(HABalanceSimpleJdbcOperations.class);

    /**
     * DB从库server列表
     */
    private ArrayList<BalanceNodeBean> serverList = new ArrayList<BalanceNodeBean>();

    /**
     * DB从库server列表用的readWriteLock
     */
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    /**
     * 备机列表(查询库)
     */
    protected List<JdbcOperations> crmB_N_JdbcList;
    /**
     * 主机ip(写库)
     */
    protected JdbcOperations       crmJdbc;

    /**
     * 构造器
     *
     * @param crmB_N_JdbcList
     */
    public HABalanceSimpleJdbcOperations(
            List<JdbcOperations> crmB_N_JdbcList,
            JdbcOperations crmJdbc) {

        this.crmB_N_JdbcList = crmB_N_JdbcList;
        this.crmJdbc = crmJdbc;
        // 生成相应的serverList
        int             len      = this.crmB_N_JdbcList.size();
        BalanceNodeBean nodeBean = null;
        for (int i = 0; i < len; i++) {
            nodeBean = new BalanceNodeBean();
            // 初始状态为正常
            nodeBean.setDownFlag(0);
            nodeBean.setCorrectionWeight(0);
            nodeBean.setCurrentWeight(0);
            // add
            this.serverList.add(nodeBean);
        }

        // Thread数
        int threadNum = 1;
        // 新建一个Threadpool
        ExecutorService              threadPool = Executors.newFixedThreadPool(threadNum);
        HABalanceJdbcHealthCheckTask task       = null;
        for (int i = 0; i < threadNum; i++) {//使用threadNum个线程去监听jdbcTemplate列表的健康状态,如果出现健康值变动,触发监听器修改节点的健康状态
            // 新作成一个Task
            task = new HABalanceJdbcHealthCheckTask(this.crmB_N_JdbcList);
            // add PropertyChangeListener
            task.addPropertyChangeListener(this);

            // 提交task到Threadpool
            threadPool.execute(task);
        }
    }

    /**
     * healthyValue属性改变通知处理。 根据healthyValue修改节点的DownFlag和权重修正系数。
     */
    public void propertyChange(PropertyChangeEvent event) {
        if (event.getPropertyName().equalsIgnoreCase("healthyValue")) {
            Object newBean = event.getNewValue();
            if (newBean instanceof NodeHealthValueBean) {
                NodeHealthValueBean valueBean = (NodeHealthValueBean) newBean;
                int                 nodeIndex = valueBean.getNodeIndex();
                //
                int healthValue = valueBean.getHealthValue();

                if (healthValue < 0) {
                    // 故障发生
                    this.setNodeDownFlag(nodeIndex, 1);
                } else {
                    // 故障恢复
                    this.setNodeDownFlag(nodeIndex, 0);
                }

                // 权重修正系数
                float correctionWeight = 0;
                if (healthValue == 0) {
                    // 设置节点的权重修正系数为0
                    correctionWeight = 0;
                } else if (healthValue == 1) {
                    // 设置节点的权重修正系数为 -0.1
                    correctionWeight = -0.1f;
                } else if (healthValue == 2) {
                    // 设置节点的权重修正系数为 -0.3
                    correctionWeight = -0.3f;
                } else if (healthValue == 3) {
                    // 设置节点的权重修正系数为 -0.5
                    correctionWeight = -0.5f;
                } else if (healthValue == 4) {
                    // 设置节点的权重修正系数为 -1.0
                    correctionWeight = -1.0f;
                }
                // 设置节点的权重修正系数
                this.setNodeCorrectionWeight(nodeIndex, correctionWeight);

                // log
                log.warn("nodeIndex=" + nodeIndex + ",healthValue="
                                 + healthValue + ",correctionWeight=" + correctionWeight);
            }
        }
    }

    /**
     * 设置节点的downFlag
     *
     * @param nodeIndex
     * @param downFlag
     */
    public void setNodeDownFlag(int nodeIndex, int downFlag) {
        // check para
        if (nodeIndex < 0 || nodeIndex >= this.serverList.size()) {
            return;
        }

        Lock writeLock = this.readWriteLock.writeLock();
        //
        writeLock.lock();
        try {
            BalanceNodeBean nodeBean = this.serverList.get(nodeIndex);
            // set downFlag
            nodeBean.setDownFlag(downFlag);
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * 设置节点的权重修正系数
     *
     * @param nodeIndex
     * @param downFlag
     */
    public void setNodeCorrectionWeight(int nodeIndex, float correctionWeight) {
        // check para
        if (nodeIndex < 0 || nodeIndex >= this.serverList.size()) {
            return;
        }

        Lock writeLock = this.readWriteLock.writeLock();
        //
        writeLock.lock();
        try {
            BalanceNodeBean nodeBean = this.serverList.get(nodeIndex);
            // set correctionWeight
            nodeBean.setCorrectionWeight(correctionWeight);
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * 使用负载均衡器取得一个可用JdbcOperations。
     *
     * @return
     */
    protected JdbcOperations getJdbcOperations() {
        JdbcOperations result      = null;
        int            resultIndex = -1;

        Lock readLock = this.readWriteLock.readLock();
        // lock
        readLock.lock();
        try {
            // 取得一个server index
            resultIndex = MyLoadBalance.getForwardServerIndex(this.serverList);
            //log.warn("resultIndex=" + resultIndex);
        } finally {
            // unlock
            readLock.unlock();
        }
        log.info("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<获取的负载均衡jdbcOperations节点resultIndex:"+ resultIndex);
        if (resultIndex >= 0) {
            result = this.crmB_N_JdbcList.get(resultIndex);
        }
        return result;
    }

    @Override
    public <T> T query(String sql, ResultSetExtractor<T> rse)
            throws DataAccessException {
        return getJdbcOperations().query(sql, rse);
    }

    @Override
    public void query(String sql, RowCallbackHandler rch)
            throws DataAccessException {
        getJdbcOperations().query(sql, rch);
    }

    @Override
    public <T> List<T> query(String sql, RowMapper<T> rowMapper)
            throws DataAccessException {
    	JdbcOperations jdbcOperation = getJdbcOperations();
    	//log.debug("当前jdbc信息:{}",ToStringBuilder.reflectionToString(jdbcOperation));
        return jdbcOperation.query(sql, rowMapper);
    }

    @Override
    public Map<String, Object> queryForMap(String sql)
            throws DataAccessException {
        return getJdbcOperations().queryForMap(sql);
    }

    @Override
    public <T> T queryForObject(String sql, RowMapper<T> rowMapper)
            throws DataAccessException {
        return getJdbcOperations().queryForObject(sql, rowMapper);
    }

    @Override
    public <T> T queryForObject(String sql, Class<T> requiredType)
            throws DataAccessException {
        return getJdbcOperations().queryForObject(sql, requiredType);
    }

    @Override
    public <T> List<T> queryForList(String sql, Class<T> elementType)
            throws DataAccessException {
        return getJdbcOperations().queryForList(sql, elementType);
    }

    @Override
    public List<Map<String, Object>> queryForList(String sql)
            throws DataAccessException {
        return getJdbcOperations().queryForList(sql);
    }

    @Override
    public SqlRowSet queryForRowSet(String sql) throws DataAccessException {
        return getJdbcOperations().queryForRowSet(sql);
    }

    @Override
    public int update(String sql) throws DataAccessException {
        return crmJdbc.update(sql);
    }

    @Override
    public int[] batchUpdate(String[] sql) throws DataAccessException {
        return crmJdbc.batchUpdate(sql);
    }


    @Override
    public <T> T query(PreparedStatementCreator psc, ResultSetExtractor<T> rse)
            throws DataAccessException {
        return getJdbcOperations().query(psc, rse);
    }

    @Override
    public <T> T query(String sql, PreparedStatementSetter pss,
                       ResultSetExtractor<T> rse) throws DataAccessException {
        return getJdbcOperations().query(sql, pss, rse);
    }

    @Override
    public <T> T query(String sql, Object[] args, int[] argTypes,
                       ResultSetExtractor<T> rse) throws DataAccessException {
        return getJdbcOperations().query(sql, args, argTypes, rse);
    }

    @Override
    public <T> T query(String sql, Object[] args, ResultSetExtractor<T> rse)
            throws DataAccessException {
        return getJdbcOperations().query(sql, args, rse);
    }

    @Override
    public <T> T query(String sql, ResultSetExtractor<T> rse, Object... args)
            throws DataAccessException {
        return getJdbcOperations().query(sql, rse, args);
    }

    @Override
    public void query(PreparedStatementCreator psc, RowCallbackHandler rch)
            throws DataAccessException {
        getJdbcOperations().query(psc, rch);
    }

    @Override
    public void query(String sql, PreparedStatementSetter pss,
                      RowCallbackHandler rch) throws DataAccessException {
        getJdbcOperations().query(sql, pss, rch);
    }

    @Override
    public void query(String sql, Object[] args, int[] argTypes,
                      RowCallbackHandler rch) throws DataAccessException {
        getJdbcOperations().query(sql, args, argTypes, rch);
    }

    @Override
    public void query(String sql, Object[] args, RowCallbackHandler rch)
            throws DataAccessException {
        getJdbcOperations().query(sql, args, rch);
    }

    @Override
    public void query(String sql, RowCallbackHandler rch, Object... args)
            throws DataAccessException {
        getJdbcOperations().query(sql, rch, args);
    }

    @Override
    public <T> List<T> query(PreparedStatementCreator psc,
                             RowMapper<T> rowMapper) throws DataAccessException {
        return getJdbcOperations().query(psc, rowMapper);
    }

    @Override
    public <T> List<T> query(String sql, PreparedStatementSetter pss,
                             RowMapper<T> rowMapper) throws DataAccessException {
        return getJdbcOperations().query(sql, pss, rowMapper);
    }

    @Override
    public <T> List<T> query(String sql, Object[] args, int[] argTypes,
                             RowMapper<T> rowMapper) throws DataAccessException {
        return getJdbcOperations().query(sql, args, argTypes, rowMapper);
    }

    @Override
    public <T> List<T> query(String sql, Object[] args, RowMapper<T> rowMapper)
            throws DataAccessException {
        return getJdbcOperations().query(sql, args, rowMapper);
    }

    @Override
    public <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... args)
            throws DataAccessException {
        return getJdbcOperations().query(sql, rowMapper, args);
    }

    @Override
    public <T> T queryForObject(String sql, Object[] args, int[] argTypes,
                                RowMapper<T> rowMapper) throws DataAccessException {
        return getJdbcOperations().queryForObject(sql, args, argTypes, rowMapper);
    }

    @Override
    public <T> T queryForObject(String sql, Object[] args,
                                RowMapper<T> rowMapper) throws DataAccessException {
        return getJdbcOperations().queryForObject(sql, args, rowMapper);
    }

    @Override
    public <T> T queryForObject(String sql, RowMapper<T> rowMapper,
                                Object... args) throws DataAccessException {
        return getJdbcOperations().queryForObject(sql, rowMapper, args);
    }

    @Override
    public <T> T queryForObject(String sql, Object[] args, int[] argTypes,
                                Class<T> requiredType) throws DataAccessException {
        return getJdbcOperations().queryForObject(sql, args, argTypes, requiredType);
    }

    @Override
    public <T> T queryForObject(String sql, Object[] args, Class<T> requiredType)
            throws DataAccessException {
        return getJdbcOperations().queryForObject(sql, args, requiredType);
    }

    @Override
    public <T> T queryForObject(String sql, Class<T> requiredType,
                                Object... args) throws DataAccessException {
        return getJdbcOperations().queryForObject(sql, requiredType, args);
    }

    @Override
    public Map<String, Object> queryForMap(String sql, Object[] args,
                                           int[] argTypes) throws DataAccessException {
        return getJdbcOperations().queryForMap(sql, args, argTypes);
    }

    @Override
    public Map<String, Object> queryForMap(String sql, Object... args)
            throws DataAccessException {
        return getJdbcOperations().queryForMap(sql, args);
    }

    @Override
    public <T> List<T> queryForList(String sql, Object[] args, int[] argTypes,
                                    Class<T> elementType) throws DataAccessException {
        return getJdbcOperations().queryForList(sql, args, argTypes, elementType);
    }

    @Override
    public <T> List<T> queryForList(String sql, Object[] args,
                                    Class<T> elementType) throws DataAccessException {
        return getJdbcOperations().queryForList(sql, args, elementType);
    }

    @Override
    public <T> List<T> queryForList(String sql, Class<T> elementType,
                                    Object... args) throws DataAccessException {
        return getJdbcOperations().queryForList(sql, elementType, args);
    }

    @Override
    public List<Map<String, Object>> queryForList(String sql, Object[] args,
                                                  int[] argTypes) throws DataAccessException {
        return getJdbcOperations().queryForList(sql, args, argTypes);
    }

    @Override
    public List<Map<String, Object>> queryForList(String sql, Object... args)
            throws DataAccessException {
        return getJdbcOperations().queryForList(sql, args);
    }

    @Override
    public SqlRowSet queryForRowSet(String sql, Object[] args, int[] argTypes)
            throws DataAccessException {
        return getJdbcOperations().queryForRowSet(sql, args, argTypes);
    }

    @Override
    public SqlRowSet queryForRowSet(String sql, Object... args)
            throws DataAccessException {
        return getJdbcOperations().queryForRowSet(sql, args);
    }

    @Override
    public int update(PreparedStatementCreator psc) throws DataAccessException {
        return crmJdbc.update(psc);
    }

    @Override
    public int update(PreparedStatementCreator psc, KeyHolder generatedKeyHolder)
            throws DataAccessException {
        return crmJdbc.update(psc, generatedKeyHolder);
    }

    @Override
    public int update(String sql, PreparedStatementSetter pss)
            throws DataAccessException {
        return crmJdbc.update(sql, pss);
    }

    @Override
    public int update(String sql, Object[] args, int[] argTypes)
            throws DataAccessException {
        return crmJdbc.update(sql, args, argTypes);
    }

    @Override
    public int update(String sql, Object... args) throws DataAccessException {
        return crmJdbc.update(sql, args);
    }

    @Override
    public int[] batchUpdate(String sql, BatchPreparedStatementSetter pss)
            throws DataAccessException {
        return crmJdbc.batchUpdate(sql, pss);
    }

    @Override
    public int[] batchUpdate(String sql, List<Object[]> batchArgs)
            throws DataAccessException {
        return crmJdbc.batchUpdate(sql, batchArgs);
    }

    @Override
    public int[] batchUpdate(String sql, List<Object[]> batchArgs,
                             int[] argTypes) throws DataAccessException {
        return crmJdbc.batchUpdate(sql, batchArgs, argTypes);
    }

    @Override
    public <T> int[][] batchUpdate(String sql, Collection<T> batchArgs,
                                   int batchSize, ParameterizedPreparedStatementSetter<T> pss)
            throws DataAccessException {
        return crmJdbc.batchUpdate(sql, batchArgs, batchSize, pss);
    }

    public List<JdbcOperations> getCrmB_N_JdbcList() {
        return crmB_N_JdbcList;
    }

    public void setCrmB_N_JdbcList(List<JdbcOperations> crmB_N_JdbcList) {
        this.crmB_N_JdbcList = crmB_N_JdbcList;
    }

    public JdbcOperations getCrmJdbc() {
        return crmJdbc;
    }

    public void setCrmJdbc(JdbcOperations crmJdbc) {
        this.crmJdbc = crmJdbc;
    }

    @Override
    public <T> T execute(ConnectionCallback<T> action)
            throws DataAccessException {
        return null;
    }

    @Override
    public <T> T execute(StatementCallback<T> action)
            throws DataAccessException {
        return null;
    }

    @Override
    public void execute(String sql) throws DataAccessException {

    }

    @Override
    public <T> T execute(PreparedStatementCreator psc,
                         PreparedStatementCallback<T> action) throws DataAccessException {
        return null;
    }

    @Override
    public <T> T execute(String sql, PreparedStatementCallback<T> action)
            throws DataAccessException {
        return null;
    }

    @Override
    public <T> T execute(CallableStatementCreator csc,
                         CallableStatementCallback<T> action) throws DataAccessException {
        return null;
    }

    @Override
    public <T> T execute(String callString, CallableStatementCallback<T> action)
            throws DataAccessException {
        return null;
    }

    @Override
    public Map<String, Object> call(CallableStatementCreator csc,
                                    List<SqlParameter> declaredParameters) throws DataAccessException {
        return null;
    }

}
